use clap::{Arg, ArgMatches, Command};
use toml_edit::{Array, DocumentMut, Item, Table, Value};

use std::fs;
use std::{fs::File, io::Write, path::PathBuf, process};

// 美化的文件样式
const MDBOOK_NICE_CSS: &[u8] = include_bytes!("assert/mdbook_nice.css");

const MDBOOK_FILES: &[(&str, &[u8])] = &[("mdbook-nice.css", MDBOOK_NICE_CSS)];

pub fn make_app() -> Command {
    Command::new("mdbook-nice")
        .about("A mdbook preprocessor for nice style")
        .subcommand(
            Command::new("install")
                .about("Install the preprocessor and its assets")
                .arg(
                    Arg::new("dest")
                        .default_value("./")
                        .help("The destination directory for the assets"),
                ),
        )
}

fn additional<'a>(doc: &'a mut DocumentMut, additional_type: &str) -> Option<&'a mut Array> {
    let doc = doc.as_table_mut();
    // println!("doc:{:#?}", doc);
    let item = doc.get_mut("output")?;
    // println!("item-0:{:#?}", item);
    let item = item.as_table_mut()?.get_mut("html")?;
    // println!("item-1:{:#?}", item);
    let item = item
        .as_table_mut()?
        .get_mut(&format!("additional-{}", additional_type))?;
    // println!("additional-{:#?}", additional_type);
    // println!("item-2:{:#?}", item);
    item.as_array_mut()
}

fn has_file(elem: &Option<&mut Array>, file: &str) -> bool {
    match elem {
        Some(elem) => elem.iter().any(|elem| match elem.as_str() {
            None => true,
            Some(s) => s.ends_with(file),
        }),
        None => false,
    }
}

fn insert_additional(doc: &mut DocumentMut, additional_type: &str, file: &str) {
    let doc = doc.as_table_mut();

    let empty_table = Item::Table(Table::default());
    let empty_array = Item::Value(Value::Array(Array::default()));
    let item = doc.entry("output").or_insert(empty_table.clone());
    let item = item
        .as_table_mut()
        .unwrap()
        .entry("html")
        .or_insert(empty_table);
    let array = item
        .as_table_mut()
        .unwrap()
        .entry(&format!("additional-{}", additional_type))
        .or_insert(empty_array);

    array
        .as_value_mut()
        .unwrap()
        .as_array_mut()
        .unwrap()
        .push(file);
    // println!("item:{:#?}", item);
}

fn add_additional_files(doc: &mut DocumentMut) -> bool {
    let mut changed = false;
    let file = "mdbook-nice.css";

    let additional_css = additional(doc, "css");
    // println!("additional-css:{:?}", additional_css);
    if !has_file(&additional_css, file) {
        insert_additional(doc, "css", file);
        changed = true;
    }
    changed
}

fn install(sub_args: &ArgMatches) {
    let root = sub_args
        .get_one::<String>("dest")
        .expect("dest is required");
    let root = PathBuf::from(root);
    // 找到配置文件
    let config = root.join("book.toml");
    if !config.exists() {
        eprintln!("Error: book.toml not found");
        process::exit(1);
    }
    //
    let toml = fs::read_to_string(&config).expect("can't read configuration file");
    let mut doc = toml
        .parse::<DocumentMut>()
        .expect("configuration is not valid TOML");

    let add = add_additional_files(&mut doc);
    if add {
        let toml = doc.to_string();
        let mut file = File::create(config).expect("can't open configuration file for writing.");
        file.write_all(toml.as_bytes())
            .expect("can't write configuration");
        println!("Added mdbook-nice.css to book.toml");
    } else {
        println!("mdbook-nice.css already exists in book.toml");
    }

    for (file, content) in MDBOOK_FILES {
        let path = root.join(file);
        if !path.exists() {
            let mut file = File::create(&path).expect("can't create file");
            file.write_all(content).expect("can't write file");
        }
    }
}

fn main() {
    let matches = make_app().get_matches();
    println!("mdbook-nice exec...");
    if let Some(sub_args) = matches.subcommand_matches("install") {
        install(sub_args);
    } else {
        eprintln!("Error: No subcommand specified");
        process::exit(1);
    }
}
